class MonthIncomeDetail:
    def __init__(self,
                 income_before_tax,  # 税前收入
                 income_after_tax,   # 税收收入
                 special_deduction,  # 专项扣除额
                 retirement_insurance,  # 养老保险
                 medical_insurance,  # 医疗保险
                 medical_insurance_income,  # 医保卡收入
                 unemployment_insurance,  # 失业保险
                 accumulation_fund,  # 公积金
                 tax  # 个税
                 ):
        # 当月税前收入
        self.income_before_tax = income_before_tax
        # 当月税后收入
        self.income_after_tax = income_after_tax
        # 专项扣除额
        self.special_deduction = special_deduction
        # 公积金
        self.retirement_insurance = retirement_insurance
        # 医疗保险扣除部分
        self.medical_insurance = medical_insurance
        # 医疗卡收入
        self.medical_insurance_income = medical_insurance_income
        # 失业保险
        self.unemployment_insurance = unemployment_insurance
        # 公积金
        self.accumulation_fund = accumulation_fund
        self.accumulation_fund_income = accumulation_fund * 2
        # 五险一金扣除总额
        self.insurance_fund_sum = retirement_insurance + medical_insurance + unemployment_insurance + accumulation_fund
        # 个税
        self.tax = tax
        self.total_income = self.income_after_tax + self.medical_insurance_income + self.accumulation_fund * 2

    def print(self):
        output_format = """
Income before tax:        %.2f *
--------> Deductions
Special deductions:       -%.2f
Retirement fund:          -%.2f
Medical insurance:        -%.2f
Unemployment insurance:   -%.2f
Accumulation fund:        -%d
Tax:                      -%.2f *
=========
Insurance and fund sum:   -%.2f *
--------> Summary
Income after tax:         %.2f *
Accumulation fund income: %d *
Medical income:           %.2f *
Monthly total income:     %.2f *
"""
        print(output_format % (self.income_before_tax,
                               self.special_deduction,
                               self.retirement_insurance,
                               self.medical_insurance,
                               self.unemployment_insurance,
                               self.accumulation_fund,
                               self.tax,
                               self.insurance_fund_sum,
                               self.income_after_tax,
                               self.accumulation_fund_income,
                               self.medical_insurance_income,
                               self.total_income))


class NewTaxCalculator:
    tax_table = {36000: [0.03, 0],
                 144000: [0.1, 2520],
                 300000: [0.2, 16920],
                 420000: [0.25, 31920],
                 660000: [0.30, 52920],
                 960000: [0.35, 85920],
                 999999999: [0.45, 181920]}
    # 公积金扣除12%
    default_accumulation_fund_rate = .12
    # 养老保险扣除8 %
    default_retirement_insurance_rate = .08
    # 医疗保险 = 2%+3元大病统筹
    default_medical_insurance_rate = .02
    # 失业保险
    default_unemployment_insurance_rate = .002

    # 2020, TODO 预测值，按照上一年的6%增速计算
    # # 2020 社保基数，用来计算养老保险，失业保险，较上年增长6%
    # social_insurance_bases_2020 = (3830, 24979)  # 上年平均工资(7855)的46%和300%
    # # 2020 医保基数，较上年增长6%
    # medical_insurance_bases_2020 = (5890, 29453)  # 上年平均工资(9262)的60%和300%
    # # 2020 公积金基数，较上年增长6%
    # accumulation_fund_bases_2020 = (2332, 29453)  # 上年最低工资2200和平均工资的300%
    # 2020 社保基数，用来计算养老保险，失业保险，较上年增长8%
    social_insurance_bases_2020 = (3902, 25450)  # 上年平均工资(7855)的46%和300%
    # 2020 医保基数，较上年增长8%
    medical_insurance_bases_2020 = (6001, 30008)  # 上年平均工资(9262)的60%和300%
    # 2020 公积金基数，较上年增长8%
    accumulation_fund_bases_2020 = (2376, 30008)  # 上年最低工资2200和平均工资的300%

    # 2019
    # 2019 社保基数，用来计算养老保险，失业保险
    social_insurance_bases_2019 = (3613, 23565)  # 上年平均工资(7855)的46%和300%
    # 2019 医保基数，TODO 很奇怪上年平均工资不一样
    medical_insurance_bases_2019 = (5557, 27786)  # 上年平均工资(9262)的60%和300%
    # 2019 公积金基数
    accumulation_fund_bases_2019 = (2200, 27786)  # 上年最低工资2200和平均工资的300%

    # 2018
    # 2018 社保基数
    social_insurance_bases_2018 = (3387, 25401)
    # 2018 医保基数
    medical_insurance_bases_2018 = (5080, 25401)
    # 2018 公积金基数
    accumulation_fund_bases_2018 = (2120, 25401)

    def __init__(self,
                 monthly_incomes,  # 每月收入数组
                 special_deductions,  # 每月专项扣除额数组
                 social_insurance_bases,  # 每月社保基数数组，每个元素为元组(floor, ceiling)，表示基数范围
                 medical_insurance_bases,  # 每月社保基数数组，每个元素为元组(floor, ceiling)，表示基数范围
                 accumulation_fund_bases,  # 每月公积金基数数组，每个元素为元组(floor, ceiling)，表示基数范围
                 ):
        self.monthly_incomes = monthly_incomes
        self.special_deductions = special_deductions
        self.social_insurance_bases = social_insurance_bases
        self.medical_insurance_bases = medical_insurance_bases
        self.accumulation_fund_bases = accumulation_fund_bases
        length = len(self.monthly_incomes)
        if length == 0:
            raise Exception("incomes array is empty")
        if length != len(self.special_deductions) or \
                length != len(self.social_insurance_bases) or \
                length != len(self.medical_insurance_bases) or \
                length != len(self.accumulation_fund_bases):
            raise Exception("incomes'length does not equal to deductions'")
        self.income_details = []

    def calculate(self):
        length = len(self.monthly_incomes)
        # n个月累扣除五险一金以及专项扣除额的税前收入
        # 累计税前收入
        accumulation_incomes_before_tax = 0
        # 累计扣除额，包括累计的五险一金，专项扣除和免税额度（每个月5000）
        accumulation_deductions_before_tax = 0
        # 累计交税额
        accumulation_tax = 0
        for i in range(length):
            monthly_income = self.monthly_incomes[i]
            # step 1 获取各类基数
            # 获取社保基数
            social_insurance_base = self.get_social_insurance_deduction_base(i)
            # 获取医保基数
            medical_insurance_base = self.get_medical_insurance_deduction_base(i)
            # 获取公积金基数
            accumulation_fund_base = self.get_accumulation_fund_deduction_base(i)

            # step 2 计算各类税前扣除额
            # 计算养老保险扣除额
            retirement_insurance_deduction = round(social_insurance_base * 0.08, 2)
            # 计算失业保险扣除额
            unemployment_insurance_deduction = round(social_insurance_base * 0.002, 2)
            # 计算医疗保险扣除额
            medical_insurance_deduction = round(medical_insurance_base * 0.02 + 3, 2)
            # 计算公积金
            accumulation_fund_deduction = int(accumulation_fund_base * 0.12)
            # 当月专项扣除额
            special_deduction = self.special_deductions[i]
            # 当月免税额，当前税法固定为500
            tax_free_deduction = 5000

            # step 3 计算各类的累计额
            # 累加税前收入总额
            accumulation_incomes_before_tax += monthly_income
            # 累加税前扣除额
            accumulation_deductions_before_tax += retirement_insurance_deduction + unemployment_insurance_deduction + \
                medical_insurance_deduction + accumulation_fund_deduction + special_deduction + tax_free_deduction

            # step 4 计算税额
            # 计算应税总收入，等于总税前收入-总扣除额
            accumulation_income_after_deduction = accumulation_incomes_before_tax - accumulation_deductions_before_tax
            # 计算税率和速算扣除数
            tax_rate_and_quick_deduction = self.get_tax_rate_and_quick_deduction(accumulation_income_after_deduction)
            # 计算当月税额
            # 个税 = (累计税前总额 - 累计五险一金总额 - 累计专项扣除总额 - 免税额度总额) * 税率 - 速算扣除数 - 当年累计个税总额
            monthly_tax = (accumulation_incomes_before_tax - accumulation_deductions_before_tax) * \
                tax_rate_and_quick_deduction[0] - tax_rate_and_quick_deduction[1] - accumulation_tax
            accumulation_tax += monthly_tax

            # step 5 生成明细
            # 税后真实收入 = 税前收入 - 五险一金 - 个税
            monthly_income_after_tax = monthly_income - retirement_insurance_deduction - \
                unemployment_insurance_deduction - medical_insurance_deduction - accumulation_fund_deduction - \
                monthly_tax
            # TODO 医保收入 = 医保缴费基数 * (2% + 年龄系数)，小于35岁系数为0.008
            medical_insurance_income = medical_insurance_base * (0.02 + 0.008)
            income_detail = MonthIncomeDetail(monthly_income,
                                              monthly_income_after_tax,
                                              special_deduction,
                                              retirement_insurance_deduction,
                                              medical_insurance_deduction,
                                              medical_insurance_income,
                                              unemployment_insurance_deduction,
                                              accumulation_fund_deduction,
                                              monthly_tax)
            self.income_details.append(income_detail)

    # 获取税率以及速算扣除数的元组
    def get_tax_rate_and_quick_deduction(self, acculumation_incomes_before_tax):
        for (level, item) in self.tax_table.items():
            if acculumation_incomes_before_tax < level:
                return item
        raise Exception("Failed to find tax rate and quick deduction")

    # 获取医保基数
    def get_medical_insurance_deduction_base(self, index):
        monthly_income = self.monthly_incomes[index]
        medical_insurance_bases = self.medical_insurance_bases[index]
        if monthly_income < medical_insurance_bases[0]:
            base = medical_insurance_bases[0]
        elif monthly_income > medical_insurance_bases[1]:
            base = medical_insurance_bases[1]
        else:
            base = monthly_income
        if base > 0:
            return base
        raise Exception("Failed to calculate medical insurance deduction base")

    # 获取社保基数
    def get_social_insurance_deduction_base(self, index):
        monthly_income = self.monthly_incomes[index]
        social_insurance_bases = self.social_insurance_bases[index]
        if monthly_income < social_insurance_bases[0]:
            base = social_insurance_bases[0]
        elif monthly_income > social_insurance_bases[1]:
            base = social_insurance_bases[1]
        else:
            base = monthly_income
        if base > 0:
            return base
        raise Exception("Failed to calculate social insurance deduction base")

    def get_accumulation_fund_deduction_base(self, index):
        monthly_income = self.monthly_incomes[index]
        accumulation_fund_bases = self.accumulation_fund_bases[index]
        if monthly_income < accumulation_fund_bases[0]:
            base = accumulation_fund_bases[0]
        elif monthly_income > accumulation_fund_bases[1]:
            base = accumulation_fund_bases[1]
        else:
            base = monthly_income
        if base > 0:
            return base
        raise Exception("Failed to calculate accumulation fund deduction base")

    def print(self):
        if len(self.income_details) == 0:
            print("No details found")
        total_income_after_tax = 0
        total_medical_income = 0
        total_accumulation_fund_income = 0
        total_tax = 0
        total_income = 0
        for index in range(len(self.income_details)):
            income_detail = self.income_details[index]
            total_income_after_tax += income_detail.income_after_tax
            total_medical_income += income_detail.medical_insurance_income
            total_accumulation_fund_income += income_detail.accumulation_fund * 2
            total_tax += income_detail.tax
            total_income += income_detail.total_income
            print("------------------ Month %d -----------------" % (index + 1), end = '')
            income_detail.print()
        print("================== Total Detail =============")
        print("""
Total income after tax: %.2f
Total medical income  : %.2f
Total accumulation fun: %d
Total tax             : %.2f
Total income          : %.2f
        """ % (total_income_after_tax, total_medical_income, total_accumulation_fund_income, total_tax, total_income))


def calculate1():
    monthly_incomes = []
    special_deductions = []
    social_insurance_bases = []
    medical_insurance_bases = []
    accumulation_fund_bases = []
    # 每个月工资
    monthly_incomes.append(38500)
    monthly_incomes.append(38500)
    monthly_incomes.append(38500)
    monthly_incomes.append(40000 + 8680)  # 4月份ipad
    monthly_incomes.append(40000)
    monthly_incomes.append(40000)
    monthly_incomes.append(40000)
    monthly_incomes.append(40000 + 3678.16)  # 8月份加班费
    monthly_incomes.append(40000)
    monthly_incomes.append(40000)
    monthly_incomes.append(40000)
    monthly_incomes.append(40000 + 7356.32) # 12月份加班费
    # 每个月的五险一金基数
    for i in range(6):
        social_insurance_bases.append(NewTaxCalculator.social_insurance_bases_2018)
        medical_insurance_bases.append(NewTaxCalculator.medical_insurance_bases_2018)
        accumulation_fund_bases.append(NewTaxCalculator.accumulation_fund_bases_2018)
    for i in range(6):
        social_insurance_bases.append(NewTaxCalculator.social_insurance_bases_2019)
        medical_insurance_bases.append(NewTaxCalculator.medical_insurance_bases_2019)
        accumulation_fund_bases.append(NewTaxCalculator.accumulation_fund_bases_2019)
    # 每月专项扣除额
    for i in range(12):
        special_deductions.append(2000)
    calculator = NewTaxCalculator(monthly_incomes,
                                  special_deductions,
                                  social_insurance_bases,
                                  medical_insurance_bases,
                                  accumulation_fund_bases)
    calculator.calculate()
    calculator.print()


def calculate2():
    monthly_incomes = []
    special_deductions = []
    social_insurance_bases = []
    medical_insurance_bases = []
    accumulation_fund_bases = []
    # 每个月工资
    monthly_incomes.append(120000)
    monthly_incomes.append(40000)
    monthly_incomes.append(40000)
    new_monthly_income = 40000
    monthly_incomes.append(new_monthly_income)  # 4月份
    monthly_incomes.append(new_monthly_income)
    monthly_incomes.append(new_monthly_income)
    monthly_incomes.append(new_monthly_income)
    monthly_incomes.append(new_monthly_income)
    monthly_incomes.append(new_monthly_income)
    monthly_incomes.append(new_monthly_income)
    monthly_incomes.append(new_monthly_income)
    monthly_incomes.append(new_monthly_income)
    # 每个月的五险一金基数
    for i in range(6):
        social_insurance_bases.append(NewTaxCalculator.social_insurance_bases_2019)
        medical_insurance_bases.append(NewTaxCalculator.medical_insurance_bases_2019)
        accumulation_fund_bases.append(NewTaxCalculator.accumulation_fund_bases_2019)
    for i in range(6):
        social_insurance_bases.append(NewTaxCalculator.social_insurance_bases_2020)
        medical_insurance_bases.append(NewTaxCalculator.medical_insurance_bases_2020)
        accumulation_fund_bases.append(NewTaxCalculator.accumulation_fund_bases_2020)
    # 每月专项扣除额
    for i in range(12):
        special_deductions.append(2000)
    calculator = NewTaxCalculator(monthly_incomes,
                                  special_deductions,
                                  social_insurance_bases,
                                  medical_insurance_bases,
                                  accumulation_fund_bases)
    calculator.calculate()
    calculator.print()


if __name__ == '__main__':
    calculate1()
